Release 10.1A: OpenEdge Development:
Programming Interfaces


Managing application user IDs in n-tier applications

An application user ID exists essentially to specify a single user identity that is shared between client and AppServer sessions of an n-tier application. The application user ID typically originates from the client and is used to set the Progress session ID in each AppServer session. It might also be used for synchronizing database connection IDs in each AppServer session, as appropriate.

In n-tier applications, the problem of managing application user IDs consists of two main issues:

  1. How to transport the application user ID between the AppServer session and a client session.
  2. How to share a single login for an application user ID across multiple requests to an AppServer running in stateless or state-free operating mode.
  3. Note: For information on AppServer operating modes and how AppServer and client sessions interact with them, see OpenEdge Application Server: Developing AppServer Applications .

So, when running in stateless or state-free operating mode, an AppServer must:

In OpenEdge, the basic mechanism for managing user identities is the client-principal object. Typically, in an n-tier application, the AppServer’s 4GL code creates and maintains the client-principal object on behalf of the client and exchanges identity information, based on the operating mode, as described in Table 2–6.

Table 2–6: Identity management for n-tier applications 
In this
operating mode...
The AppServer...
State-aware or state-reset
Maintains a single client connection to a given agent. So, the agent only needs to assert and maintain the user’s identity using a single client-principal object during the entire connection. The agent can then remove the client-principal object from session context when the client disconnects.
Stateless
Maintains multiple client connections to the broker, which distributes client requests to any available agent. Because the broker maintains client connections, a shared client-principal object can be identified using the SERVER-CONNECTION-ID on the SESSION system handle. However, this value is only unique within an AppServer session. For auditing across multiple AppServer sessions, you might use a universal unique identifier (UUID) generated by the 4GL GENERATE-UUID function to uniquely identify any recorded client login sessions, and use the SERVER-CONNECTION-ID on the SESSION system handle to key access to the client-principal object in user context storage.
State-free
Maintains no client connections. The broker distributes client requests as they arrive to any available agent. Because the broker maintains no client connections, the shared client-principal object must be identified based on a unique identifier, which you can generate using the 4GL GENERATE-UUID function. The AppServer creates this identifier for each client-principal object that it creates and communicates it as a security token to the client in response to its initial login request to the AppServer. The client must then return this security token with each request to the AppServer, so the AppServer agent can retrieve the shared client-principal object from user context storage.

Initializing a client-principal object for an n-tier application

The following code fragment shows how you might initialize a client-principal object to account for the type of Progress session environment it is used in:

Client-principal object initialization for n-tier applications
CREATE CLIENT-PRINCIPAL m_hCP. 
m_hCP:USER-ID = p_cUserId. 
m_hCP:DOMAIN-NAME = p_cAuthDomain. 
IF (SESSION:REMOTE) THEN DO: /* AppServer session */ 
    IF (SESSION:SERVER-OPERATING-MODE = "State-free") THEN DO: 
        cSessionID = BASE64-ENCODE(GENERATE-UUID).  
        m_hCP:SESSION-ID = SUBSTRING(cSessionID, 1, 22)  
    END. 
    ELSE DO: /* Session-managed operating modes */ 
        m_hCP:SESSION-ID = SESSION:SERVER-CONNECTION-ID.  
    END.  
END.  
ELSE DO: /* Client session */ 
    cSessionID = BASE64-ENCODE(GENERATE-UUID). 
    m_hCP:SESSION-ID = SUBSTRING(cSessionID, 1, 22) 
END. 
m_hCP:AUDIT-EVENT-CONTEXT = p_cUserId + “@” + p_cAuthDomain. 
m_hCP:CLIENT-TTY = SESSION:CLIENT-TYPE + "." + SESSION:DISPLAY-TYPE 

For any of the session-managed operating modes, the fragment sets the SESSION-ID attribute to the SERVER-CONNECTION-ID attribute on the SESSION system handle. This value uniquely identifies the client connection for a given AppServer session and is especially useful for an AppServer session running in stateless operating mode. Since a stateless application service can identify each request by the client connection, it can use this client connection identifier for the client-principal object that represents a client identity. However, for stateless auditing purposes, you might also use the SERVER-CONNECTION-ID value to key references to a shared client-principal object, but set the SESSION-ID attribute to a universal unique identifier (UUI) using the 4GL GENERATE-UUID function. This value ensures that a unique client login session auditing record across all possible AppServer sessions.

For a state-free AppServer operating mode, the fragment sets the SESSION-ID attribute on the client-principal object to a universal unique identifier (UUID), which is generated using the 4GL GENERATE-UUID function and encoded as Base64 using the 4GL BASE64-ENCODE function. For more information on these functions, see the "Creating and managing unique object identities" section. Since clients of a state-free AppServer make no connections to the AppServer, the application service needs to maintain its own unique identifiers for the client-principal objects that represent client identities.

Both stateless and state-free application services must manage user context in order to export, store, retrieve, and import different client-principal objects between client requests, but they must do it differently according to their respective session-managed and session-free behavior.

Managing user identities for a stateless application service

In a stateless application service, the process of managing user identities is somewhat simplified by the existence of client connections. Following is a typical procedure for managing stateless user identities.

To manage users identities for a stateless application service, you can do the following:

  1. In the AppServer’s configured startup procedure, build any required application trusted domain registry and remove any client-principal objects from a previous user context left-over from any abnormal session termination.
  2. In the application service’s login procedure:
    1. Initialize and seal the client-principal object using a UUID (to uniquely set any auditing context) as the value for the SESSION-ID attribute on the client-principal object.
    2. Export the client-principal object.
    3. Store the exported RAW value of the client-principal object in a context database keyed on the SESSION:SERVER-CONNECTION-ID attribute value.
    4. For example:

      DEFINE SHARED VARIABLE hCP AS HANDLE NO-UNDO. 
      DEFINE VARIABLE rCP AS RAW NO-UNDO. 
      DEFINE SHARED VARIABLE cAccessCode AS CHARACTER NO-UNDO. 
      DEFINE VARIABLE cSessionID AS CHARACTER NO-UNDO. 
      CREATE CLIENT-PRINCIPAL hCP. 
      /* Initialize, seal, and login client-principal object */ 
      ... 
      cSessionID = BASE64-ENCODE(GENERATE-UUID). 
      hCP:SESSION-ID = SUBSTRING(cSessionID, 1, 22) 
      hCP:SEAL(cAccessCode). 
      /* Export client-princpal object */ 
      rCP = hCP:EXPORT-PRINCIPAL(). 
      /* Store exported client-principal by the connection ID */ 
      CREATE UserContext NO-ERROR. 
      ASSIGN UserContext.Principal-key = SESSION:SERVER-CONNECTION-ID  
             UserContext.Principal     = rCP NO-ERROR. 
      ... 
      

  3. In the AppServer’s configured activation procedure:
    1. Lookup the exported client-principal object in the context database using the SESSION:SERVER-CONNECTION-ID value as the lookup key.
    2. Import the client principal object.
    3. Validate the imported client-principal seal, and use the validated client principal object to set the user identity.
    4. For example:

      DEFINE SHARED VARIABLE hCP AS HANDLE NO-UNDO. 
      DEFINE VARIABLE rCP AS RAW NO-UNDO. 
      CREATE CLIENT-PRINCIPAL hCP. 
      /* Lookup client-principal object in context database */ 
      FIND UserContext  
          WHERE Principal-key = SESSION:SERVER-CONNECTION-ID NO-ERROR. 
      rCP = UserContext.Principal. 
      /* Import client-princpal object, validate, and set user identity */ 
      hCP:IMPORT-PRINCIPAL(rCP). 
      IF SECURITY-POLICY:SET-CLIENT(hCP) THEN /* Identity is valid */ 
          ... 
      ELSE /* Identity is not valid */ 
          ... 
      

  4. In all non-login procedures for the application service, verify that the user identity is valid from activation procedure, and if it is, perform the specified application service task. For example:
  5. DEFINE SHARED VARIABLE hCP AS HANDLE NO-UNDO. 
    DEFINE SHARED VARIABLE cAccessCode AS CHARACTER NO-UNDO. 
    IF hCP:VALIDATE-SEAL(cAccessCode) THEN /* Identity is valid */ 
        ... /* Perform application service task */ 
    ELSE /* Identity is not valid */ 
        ... 
    

  6. In the AppServer’s configured deactivation procedure, clear the Progress session ID for the next user. For example:
  7. DEFINE SHARED VARIABLE hCP AS HANDLE NO-UNDO. 
    hCP:LOGOUT(). 
    DELETE OBJECT hCP. 
    ... 
    

  8. In the application service’s logout procedure:
    1. Lookup the exported client-principal object in the context database using the SESSION:SERVER-CONNECTION-ID value as the lookup key.
    2. Remove the exported client-principal object from the context database.
    3. Note: The client-principal object is already imported using the activation procedure and its identity will be cleared using the deactivation procedure.

      For example:

      /* Lookup client-principal object and remove from context database */ 
      FIND UserContext  
          WHERE Principal-key = SESSION:SERVER-CONNECTION-ID NO-ERROR. 
      DELETE UserContext NO-ERROR. 
      ... 
      

  9. In the AppServer’s configured shutdown procedure, empty the user context database. For example:
  10. /* Empty the context database */ 
    FOR EACH UserContext: 
        DELETE UserContext NO-ERROR. 
    END. 
    ... 
    

Managing user identities for a state-free application service

In a state-free application service, the process of managing user identities is somewhat complicated by the lack of any client connections. This requires the application service to manage user identities independently of any physical client identity and to provide a means to exchange the client’s identity with the application service on every client request. Note that for a state-free AppServer, there are no configurable activation and deactivation procedures. So, all the work of establishing and clearing user identities for a given client request must be done for each and every service call by the application service itself. Following is a typical procedure for managing state-free user identities.

To manage users identities for a state-free application service, you can do the following:

  1. In the AppServer’s configured startup procedure, build any required application trusted domain registry and remove any client-principal objects from a previous user context left-over from any abnormal session termination.
  2. In the application service’s login procedure:
    1. Initialize and seal the client-principal object using a UUID to set a unique value for the SESSION-ID attribute on the client-principal object.
    2. Export the client-principal object.
    3. Store the exported RAW value of the client-principal object in a context database keyed on the SESSION-ID attribute value.
    4. Return the SESSION-ID attribute value as an output parameter to the client.
    5. For example:

      DEFINE OUTPUT PARAMETER cSessionID AS CHARACTER NO-UNDO. 
      DEFINE SHARED VARIABLE hCP AS HANDLE NO-UNDO. 
      DEFINE VARIABLE rCP AS RAW NO-UNDO. 
      DEFINE VARIABLE cAccessCode AS CHARACTER NO-UNDO. 
      CREATE CLIENT-PRINCIPAL hCP. 
      /* Initialize, seal, and login client-principal object */ 
      ... 
      cSessionID = BASE64-ENCODE(GENERATE-UUID). 
      hCP:SESSION-ID = SUBSTRING(cSessionID, 1, 22) 
      hCP:SEAL(cAccessCode). 
      /* Export client-princpal object */ 
      rCP = hCP:EXPORT-PRINCIPAL(). 
      /* Store exported client-principal */ 
      CREATE UserContext NO-ERROR. 
      ASSIGN UserContext.Principal-key = hCP:SESSION-ID  
             UserContext.Principal     = rCP NO-ERROR. 
      /* Return security token to client */ 
      cSessionID = hCP:SESSION-ID. 
      ... 
      

  3. In all non-login procedures for the application service:
    1. Pass in security token as input parameter.
    2. Lookup the exported client-principal object in the context database using the security token value as the lookup key.
    3. Import the client principal object.
    4. Validate the imported client-principal seal, and use the validated client principal object to set the user identity.
    5. Perform application service task.
    6. Clear the Progress session ID for the next user.
    7. For example:

      DEFINE INPUT PARAMETER cSessionID AS CHARACTER NO-UNDO. 
      DEFINE SHARED VARIABLE hCP AS HANDLE NO-UNDO. 
      DEFINE VARIABLE rCP AS RAW NO-UNDO. 
      CREATE CLIENT-PRINCIPAL hCP. 
      /* Lookup client-principal object in context database */ 
      FIND UserContext  
          WHERE Principal-key = cSessionID NO-ERROR. 
      rCP = UserContext.Principal. 
      /* Import client-princpal object, validate, and set user identity */ 
      hCP:IMPORT-PRINCIPAL(rCP). 
      IF SECURITY-POLICY:SET-CLIENT(hCP) THEN /* Identity is valid */ 
          ... /* Perform application service task */ 
      ELSE /* Identity is not valid */ 
          ... 
      /* Clear user ID for next user */ 
      hCP:LOGOUT(). 
      DELETE OBJECT hCP. 
      ... 
      

  4. In the application service’s logout procedure:
    1. Pass in security token as input parameter.
    2. Lookup the exported client-principal object in the context database using the security token value as the lookup key.
    3. Remove the exported client-principal object from the context database.
    4. For example:

      DEFINE INPUT PARAMETER cSessionID AS CHARACTER NO-UNDO. 
      /* Lookup client-principal object and remove from context database */ 
      FIND UserContext  
          WHERE Principal-key = cSessionID NO-ERROR. 
      DELETE UserContext NO-ERROR. 
      ... 
      

  5. In the AppServer’s configured shutdown procedure, empty the user context database. For example:
  6. /* Empty the context database */ 
    FOR EACH UserContext: 
        DELETE UserContext NO-ERROR. 
    END. 
    ... 
    


Copyright © 2005 Progress Software Corporation
www.progress.com
Voice: (781) 280-4000
Fax: (781) 280-4095